home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-03 / qbs-0101.zip / FEATURE.QBS < prev    next >
Text File  |  1993-01-04  |  31KB  |  881 lines

  1. ------------------------------------------------------------------------
  2.   The QuickBASIC Scrapbook                               Special Feature    
  3.                                                          ---------------
  4.   Vol 1, Issue 1                                            January 1993
  5. ------------------------------------------------------------------------
  6.    There are three parts to this special feature: Introduction, 
  7.  QuickBASIC code, and Assembler code. Special thanks to Rich
  8.  Geldreich, who took many days to develop this code, then release
  9.  it into the public domain. Thanks, Rich!
  10.  
  11.  
  12. ════════════════════════════════════════════════════════════════════════
  13.  Area:    QuickBasic
  14.   Msg:    #13647
  15.  Date:    11-30-92 21:12 (Public) 
  16.  From:    RICH GELDREICH           
  17.  To:      ALL                      
  18.  Subject: Mod Player                                      (Introduction)
  19. ────────────────────────────────────────────────────────────────────────
  20.  
  21.     Due to the fact the Cornel Huth will be releasing a Shareware MOD 
  22. player in the very near future, I have decided to flood the QuickBASIC 
  23. information net's with as much MOD playing code/information as possible. 
  24. The following messages contain a working MOD player written in almost 
  25. pure-QB, the ASM module takes care of PC-Speaker control, which QB is 
  26. not good at.
  27.  
  28.     The QB code is documented, so it shouldn't be too hard to follow. 
  29. Sorry, the ASM code is still a little bare.
  30.  
  31.     Sound Blaster and other soundcards can be supported by making 
  32. modifications to the ASM buffer player. I will post these mods if anyone 
  33. is interested in them.
  34.  
  35.     Rich
  36.  
  37.  
  38.     PS. I will do my best to optimize the ASM version of this player so 
  39. it can be small enough to post on this conference.
  40.  
  41. --- MsgToss 2.0b
  42.  * Origin: Computer Co-Op - Voorhees, NJ | Ted Hare (1:266/29)
  43.  
  44. ════════════════════════════════════════════════════════════════════════════════
  45.  Area:    QuickBasic
  46.   Msg:    #13648 - #13654
  47.  Date:    11-30-92 21:14 (Public) 
  48.  From:    RICH GELDREICH           
  49.  To:      ALL                      
  50.  Subject: MOD Player: QuickBASIC version             
  51. ────────────────────────────────────────────────────────────────────────────────
  52. 'QBMP15.BAS (experiment 26-parallel substager/mixer & looping overmix)
  53. 'A PDS/QB4.5 4-Channel Amiga MOD Player
  54. 'Written by Rich Geldreich (C) Copyright November 27, 1992
  55. 'You may use this program for anything you wish, but if you're going to
  56. 'make any money off it I would like to know about it first.
  57. '
  58. 'NOTES: This program also requires INTRPT.OBJ (from QB.LIB or QBX.LIB),
  59. 'and QBMPASM.ASM to function. I have only tested this program in
  60. 'PDS 7.1. I *highly* recommend that you only run this program compiled!
  61. '
  62. 'To compile(this is a pretty complicated procedure for beginners):
  63. '-First, you must extract INTRPT.OBJ from QB.LIB or QBX.LIB(depending
  64. '-on QB/BC7). Type:
  65. 'LIB QBX.LIB *INTRPT.OBJ,;              [BC7]
  66. 'LIB QB.LIB *INTRPT.OBJ,;               [QB4.5]
  67. '-Then make a QLB by:
  68. 'LINK /q QBMPASM+INTRPT,,,QBXQLB,;      [BC7]
  69. 'LINK /q QBMPASM+INTRPT,,,BQLB45        [QB4.5]
  70. '-And a LIB by:
  71. 'LIB QBMPASM QBMPASM+INTRPT,;           [BC7 or QB4.5]
  72. '-Then load QB(or QBX) with the following and compile:
  73. 'QB(x) /lQBMPASM QBMP15
  74. 'Please note that the above instructions for QB4.5 haven't been tested.
  75. '
  76. 'If you have a fast computer and want better sound change the variable
  77. '"IntRate=11000" below to a larger number(max is about 19,000 hz). This
  78. 'program is currently configured to load MODs up to about 300k, change
  79. 'the line that says "Null& = SETMEM(-300000)" to load larger MODs.
  80. 'Anyone having problems or questions and can afford a L/D call:
  81. '(609)-742-8752 between 3:00pm and 1:00am eastern time.
  82. '
  83. 'If you get it compiled successfully, then run it with:
  84. 'QBMP15 modfile.MOD
  85. 'You should hear the MOD play on your PC-Speaker. The keys 1-4 turn 
  86. on/off
  87. 'each channel, and the left and right arrows fast forward/rewind the 
  88. MOD.
  89. 'Press escape to drop back to DOS. Here goes!! -RG
  90. DEFINT A-Z
  91. CONST True = -1, False = 0
  92. DECLARE FUNCTION CheckDoneFlag% ()
  93. DECLARE SUB SetBuffers (BYVAL BDS%, BYVAL Buffer1%, BYVAL Buffer2%, _
  94. BYVAL BufferSize%)
  95. DECLARE FUNCTION GetCS% ()
  96. DECLARE FUNCTION GetOF% ()
  97. DECLARE FUNCTION UnsignedComp% (BYVAL A%, BYVAL B%)
  98.  
  99. DECLARE SUB SetInt8Rate (A&)
  100. DECLARE SUB SpeakerOff ()
  101. DECLARE SUB SpeakerOn ()
  102. DECLARE FUNCTION Alloc% (A%)
  103. DECLARE SUB ExitWithError (A$)
  104. DECLARE FUNCTION Extract% (A$, offset%)
  105.  
  106. TYPE RegTypeX
  107.      ax    AS INTEGER
  108.      bx    AS INTEGER
  109.      cx    AS INTEGER
  110.      dx    AS INTEGER
  111.      bp    AS INTEGER
  112.      si    AS INTEGER
  113.      di    AS INTEGER
  114.      flags AS INTEGER
  115.      ds    AS INTEGER
  116.      es    AS INTEGER
  117. END TYPE
  118. DIM SHARED CPU as RegTypeX
  119.  
  120. 'Sample arrays.
  121. DIM S.Name(30) AS STRING * 22
  122. DIM S.Volume(30)
  123. DIM S.Segment(30)
  124. DIM S.RepStart(30),     S.RepLength(30)
  125. DIM S.Length(30)
  126.  
  127. 'Channel arrays
  128. DIM C.InactiveFlag(3)
  129. DIM C.Volume(3)
  130. DIM C.Segment(3)
  131. DIM C.Period(3),        C.HighStep(3),  C.LowStep(3)
  132. DIM C.Offset(3),        C.Remainder(3)
  133. DIM C.RepStart(3),      C.RepLength(3), C.LoopEnd(3)
  134. DIM C.Length(3)
  135.  
  136. 'For command processing
  137. DIM C.Command(3)
  138. DIM C.PortSpeed(3)
  139. DIM C.PortDest(3)
  140. DIM C.VolumeSpeed(3)
  141.  
  142. 'Misc. Arrays
  143. DIM PeriodHigh(1023),   PeriodLow(1023) 'Precalculated step rates
  144. DIM VolumeTable(63, 255) 'Precalculated volume tables
  145. DIM SampleBuffer(1199)   'Sample buffer;for mixing+playing at same time.
  146. DIM PatternSegment(127)  'Holds segment's of all the patterns to play
  147. DIM ActiveChannels(3)    'Holds active channels while mixing
  148. DIM ChannelOn(3)
  149.  
  150. DIM Scale8to6(255)       'translation table for dividing each signed
  151.                          'sample by 4
  152. '=====================================================================
  153. ON ERROR GOTO ErrorHandler
  154.  
  155. PRINT "QBMP v1.5 - An Amiga MOD player written in PDS/QuickBASIC."
  156. PRINT "(C) Copyright 1992 By Rich Geldreich"
  157. 'Precalculate an 8-bit to 6-bit signed translation table
  158. FOR A=-128 to 127
  159.     C=A\4
  160.     IF A<0 then Scale8to6(A+256)=C ELSE Scale8to6(A)=C
  161. NEXT
  162.  
  163. 'Free up 300,000 bytes of far memory.
  164. Null& = SETMEM(-300000)
  165.  
  166. FileSpec$ = Command$
  167. IF INSTR(FileSpec$,".")=0 THEN FileSpec$=FileSpec$+".MOD"
  168. 'Attempt to open the MOD file.
  169. OPEN FileSpec$ FOR INPUT AS #1:CLOSE #1
  170. OPEN FileSpec$ FOR BINARY AS #1
  171.  
  172. 'Check to see if MOD contains 15 or 31 samples...
  173. A$ = "    ": GET #1, 1081, A$
  174. 'If the string at offset 1081 is "M.K.", or the first 3 letters are
  175. '"FLT", then the MOD contains 31 samples:
  176. S.Max = 15 - 16 * ((A$ = "M.K.") OR (LEFT$(A$, 3) = "FLT"))
  177.  
  178. 'Print the MOD's title
  179. A$ = SPACE$(20): GET #1, 1, A$
  180. PRINT "Title: ";A$
  181. '=====================================================================
  182. A$ = SPACE$(8)
  183. FOR A = 0 TO S.Max - 1
  184.     'Skip the sample's name
  185.     GET #1, , S.Name(A)
  186.     'Get the info on the sample
  187.     GET #1, , A$
  188.     S.Volume(A)         = ASC(MID$(A$, 4, 1))
  189.     IF S.Volume(A) > 64 THEN S.Volume(A) = 64
  190.     S.Length(A)         = Extract(A$, 1)
  191.     S.RepStart(A)       = Extract(A$, 5)
  192.     S.RepLength(A)      = Extract(A$, 7)
  193.     IF S.RepLength(A) = 2 THEN S.RepLength(A) = 0
  194.     IF S.Length(A)=2 THEN S.Length(A)=0
  195. NEXT
  196. '=====================================================================
  197. A$ = " ": GET #1, , A$: T.Length = ASC(A$): GET #1, , A$
  198. 'Load the pattern table.
  199. HighestPattern = -1
  200. FOR A = 0 TO 127
  201.     GET #1, , A$: B = ASC(A$)
  202.     IF B > HighestPattern THEN HighestPattern = B
  203.     PatternSegment(A) = B
  204. NEXT
  205. '=====================================================================
  206. IF S.Max = 31 THEN SEEK #1, LOC(1) + 5
  207. A$ = SPACE$(1024)
  208. 'Load the patterns.
  209. FOR A = 0 TO HighestPattern
  210.     LOCATE , 1: PRINT USING "Parsing Pattern ##"; A;
  211.     GET #1, , A$
  212.     B = Alloc(80): DEF SEG = B: C = 0
  213.  
  214.     e = 1
  215.     FOR d = 1 TO 256
  216.         b1 = ASC(MID$(A$, e, 1))
  217.         b2 = ASC(MID$(A$, e + 1, 1))
  218.         b3 = ASC(MID$(A$, e + 2, 1))
  219.         b4 = ASC(MID$(A$, e + 3, 1))
  220.         e = e + 4
  221.         sample = (b1 AND 240) OR (b3 \ 16)
  222.         period = (b1 AND 15) * 256 OR b2
  223.         effect = b3 AND 15
  224.         operand = b4
  225.  
  226.         IF sample > S.Max THEN sample = 0
  227.         IF period > 1023 OR period < 20 THEN period = 0
  228.  
  229.         SELECT CASE effect
  230.         CASE &HC
  231.             IF operand > 64 THEN operand = 64
  232.         END SELECT
  233.         'Store the channel in an expanded format for ease of processing.
  234.         POKE C, sample
  235.         POKE C + 1, period \ 256
  236.         POKE C + 2, period AND 255
  237.         POKE C + 3, effect
  238.         POKE C + 4, operand
  239.         C = C + 5
  240.     NEXT
  241.     'Fill in the pattern's segment in the position table.
  242.     FOR d = 0 TO 127:IF PatternSegment(d)=A THEN PatternSegment(d)=B
  243.     NEXT
  244. NEXT
  245. '=====================================================================
  246. 'Load the samples.
  247. FOR A = 0 TO S.Max - 1
  248.     LOCATE , 1: PRINT USING "Loading Sample ##  "; A + 1;
  249.  
  250.     d& = S.Length(A): IF d& < 0 THEN d& = d& + 65536
  251.     'Allocate 1024 bytes more than needed for mixer runoff.
  252.     d& = d& + 1024
  253.     IF d& > 65530 THEN ExitWithError "Sample Too Large"
  254.  
  255.     B = Alloc(d& \ 16 + 1) 'Allocate memory for the sample.
  256.     S.Segment(A) = B
  257.  
  258.     'Load the sample
  259.     CPU.ax = &H3F00
  260.     CPU.bx = FILEATTR(1, 2)
  261.     CPU.ds = B: CPU.dx = 0
  262.     CPU.cx = S.Length(A)
  263.  
  264.     CALL interruptx(&H21, CPU, CPU)
  265.     IF (CPU.Flags AND 1) THEN ExitWithError "Error Loading Sample"
  266.  
  267.     DEF SEG = B
  268.  
  269.     A& = S.Length(A): IF A& < 0 THEN A& = A& + 65536
  270.  
  271.     'Divide each byte of the sample by 4 for mixing. A lookup table is
  272.     'used because QB doesn't support signed bytes.
  273.     FOR B& = 0 TO A& - 1
  274.         POKE B&, Scale8to6(PEEK(B&))
  275.     NEXT
  276.  
  277.     'Clear the end of the sample for mixer runoff.
  278.     FOR A& = A& TO A& + 1023
  279.         POKE A&, 0
  280.     NEXT
  281. NEXT
  282. '=====================================================================
  283. LOCATE ,1:PRINT SPACE$(40);
  284.  
  285. IntRate = 11000      'Interrupt speed, in samples per second.
  286.  
  287. 'Figure out how many samples per 1/50th of a second.
  288. IntsPerClick = IntRate \ 50
  289.  
  290. 'Precalculate a step for each period. The constant &H369040 is from the
  291. 'Amiga, it is scaled up by 256 (&h100) so floating point math can be
  292. 'eliminated.
  293. K& = &H36904000 \ IntRate
  294. FOR A = 20 TO 1023
  295.     A& = K& \ A
  296.     PeriodHigh(A) = A& \ 256
  297.     PeriodLow(A) = CINT(A&) AND 255
  298. NEXT
  299. 'Precalculate the volume lookup tables. Enables the mixer to adjust the
  300. 'volume of a sample with slow multiples and divides.
  301. FOR A = 0 TO 63
  302.     FOR B = -128 TO -1
  303.         C = (B * A) \ 64: IF C < 0 THEN C = C + 256
  304.         VolumeTable(A, B + 256) = C
  305.     NEXT
  306.     FOR B = 0 TO 127
  307.         VolumeTable(A, B) = (B * A) \ 64
  308.     NEXT
  309. NEXT
  310.  
  311. 'Make all channels inactive, and enable them.
  312. FOR A = 0 TO 3: C.InactiveFlag(A) = True: ChannelOn(A)=True:NEXT
  313.  
  314. BufferOffset    = 512   'Current mixing offset.
  315. T.Tempo         = 6     'Default tempo is 6/50th of a second.
  316. T.ClicksLeft    = 6     'Clicks left before a line.
  317. T.Pos           = 0     'Lines left before a new pattern.
  318. GOSUB StartNewPattern
  319. GOSUB DoLine
  320.  
  321. DEF SEG=&H0
  322. 'Save old interrupt 8 handler.
  323. CPU.ax = &H3508:CALL interruptx(&H21, CPU, CPU)
  324. Old8.Offset = CPU.bx:Old8.Segment = CPU.es
  325. 'Initialize the assembly buffer player.
  326. SetBuffers VARSEG(SampleBuffer(0)), VARPTR(SampleBuffer(0)), _
  327.            VARPTR(SampleBuffer(512)), IntsPerClick * 2
  328.  
  329. 'Set int 8 to our asm routine
  330. CPU.ax = &H2508:CPU.ds = GetCS:CPU.dx = GetOF
  331. CALL interruptx(&H21, CPU, CPU)
  332.  
  333. 'Reprogram the 8255's timer to the specified sample rate.
  334. SetInt8Rate &H1234DE \ IntRate
  335.  
  336. 'Turn speaker on, and play the MOD.
  337. SpeakerOn
  338. DO
  339.  
  340.     'Wait for sync signal from the assembly buffer player...
  341.     DO: LOOP UNTIL CheckDoneFlag
  342.  
  343.     'Mix another buffer.
  344.     GOSUB DoMix
  345.  
  346.     A$=INKEY$
  347.     IF A$<>"" THEN  'Process any keystrokes.
  348.         K=ASC(RIGHT$(A$,1))
  349.         SELECT CASE K
  350.         CASE 49 TO 52
  351.              ChannelOn(K-49) = NOT ChannelOn(K-49)
  352.         CASE 27
  353.              Exit do
  354.         CASE &H4b 'Left
  355.             T.Pos=T.Pos-1
  356.             IF T.Pos<0 THEN T.Pos=T.Length-1
  357.             GOSUB StartNewPattern
  358.         CASE &H4d 'Right
  359.             T.Pos=T.Pos+1
  360.             IF T.Pos=>T.Length THEN T.Pos=0
  361.             GOSUB StartNewPattern
  362.         END SELECT
  363.    END IF
  364.  
  365. LOOP
  366. 'Turn off speaker.
  367. SpeakerOff
  368. 'Set int 8 rate to normal (18.2 hz)
  369. SetInt8Rate 0
  370.  
  371. 'restore old int 8 handler
  372. CPU.ax = &H2508:CPU.ds = Old8.Segment:CPU.dx = Old8.Offset
  373. CALL interruptx(&H21, CPU, CPU)
  374.  
  375. LOCATE ,1:PRINT SPACE$(40);
  376. END
  377. '=====================================================================
  378. 'Processes 1 line(4 channels) of a pattern.
  379. DoLine:
  380.     DEF SEG = PatternSegment
  381.  
  382.     FOR A = 0 TO 3
  383.         C = PEEK(T.Offset): T.Offset = T.Offset + 1
  384.  
  385.         IF C <> 0 THEN  'Process a new sample, if any.
  386.             C = C - 1
  387.             C.Segment(A)        = S.Segment(C)
  388.             C.Volume(A)         = S.Volume(C)
  389.             C.RepStart(A)       = S.RepStart(C)
  390.             C.RepLength(A)      = S.RepLength(C)
  391.             C.Length(A)         = S.Length(C)
  392.             C.LoopEnd(A)        = C.Length(A)
  393.         END IF
  394.  
  395.         C = PEEK(T.Offset) * 256 + PEEK(T.Offset + 1)
  396.         T.Offset = T.Offset + 2
  397.         IF C <> 0 THEN  'Process a new period, if any.
  398.             IF PEEK(T.Offset) <> 3 THEN
  399.                 C.Period(A)     = C
  400.                 C.HighStep(A)   = PeriodHigh(C) 'Lookup the step rate of
  401.                 C.LowStep(A)    = PeriodLow(C)  'the new period.
  402.                 C.Offset(A)     = 0
  403.                 C.LoopEnd(A)    = C.Length(A)
  404.                 C.Remainder(A)  = -256
  405.                 C.InactiveFlag(A) = False
  406.             END IF
  407.         END IF
  408.  
  409.         C.Command(A) = 0
  410.         M = PEEK(T.Offset)
  411.         IF M<>0 THEN    'Process a command, if any.
  412.             o = PEEK(T.Offset + 1)
  413.             SELECT CASE M
  414.             CASE 12             'Volume
  415.                 C.Volume(A) = o
  416.             CASE 15             'Tempo
  417.                 T.Tempo = o
  418.             CASE 1              'Port Down
  419.                 C.Command(A)   = 2
  420.                 C.PortSpeed(A) = O
  421.             CASE 2              'Port Up
  422.                 C.Command(A)   = 3
  423.                 C.PortSpeed(A) = O
  424.             CASE 3              'Port to Note
  425.                 IF C.Period(A) > C THEN
  426.                     C.Command(A) = 4
  427.                 ELSE
  428.                     C.Command(A) = 5
  429.                 END IF
  430.                 C.PortSpeed(A) = o
  431.                 C.PortDest(A)  = C
  432.             CASE 10             'Volume Slide
  433.                 C.Command(A) = 1
  434.                 IF O AND 15 THEN
  435.                     C.VolumeSpeed(A) = -(O AND 15)
  436.                 ELSE
  437.                     C.VolumeSpeed(A) = O\16
  438.                 END IF
  439.             CASE 11             'Position Jump
  440.                 IF o<T.Length THEN
  441.                     T.Pos = o
  442.                     T.Line = 1
  443.                 END IF
  444.             CASE 13             'Pattern Skip
  445.                 T.Line = 1
  446.             END SELECT
  447.         END IF
  448.         T.Offset = T.Offset + 2
  449.     NEXT
  450.     T.ClicksLeft = T.Tempo
  451.     T.Line = T.Line - 1:IF T.Line = 0 THEN GOTO NewPattern
  452. RETURN
  453. NewPattern:
  454.     T.Pos = T.Pos + 1:IF T.Pos >= T.Length THEN T.Pos = 0
  455.     GOSUB StartNewPattern
  456. RETURN
  457. StartNewPattern:
  458.     LOCATE ,1
  459.     PRINT USING "Playing:###"; (T.Pos*100&)\T.Length;
  460.     PRINT "%";
  461.     T.Line              = 64
  462.     PatternSegment      = PatternSegment(T.Pos)
  463.     T.Offset            = 0
  464. RETURN
  465. '=====================================================================
  466. 'Main mixer follows. While the assembly routine is playing one buffer,
  467. 'this routine mixes the other.
  468. DoMix:
  469.     BufferOffset = BufferOffset XOR 512
  470.  
  471.     Tm=0
  472.     FOR K = 0 TO 3
  473.         IF C.InactiveFlag(K)=False AND ChannelOn(K) THEN
  474.             GOSUB Mix
  475.             Tm = 1
  476.         END IF
  477.     NEXT
  478.     If Tm=0 then 'If all channels inactive then just clear buffer to 
  479. 0's.
  480.         FOR B = BufferOffset TO BufferOffset + IntsPerClick-1
  481.             SampleBuffer(B) = 0
  482.         NEXT
  483.     END IF
  484.  
  485.     T.ClicksLeft = T.ClicksLeft - 1
  486.     'If not time for a new line the process slide commands, if any.
  487.     IF T.ClicksLeft = 0 THEN GOSUB DoLine ELSE GOSUB DoCommands
  488. RETURN
  489. '=====================================================================
  490. Mix:
  491.     MixesLeftToDo   = IntsPerClick
  492.     OffsetNow       = BufferOffset
  493.  
  494.     'Preload all needed variables for speed.
  495.     DEF SEG = C.Segment(K)
  496.     o = C.Offset(K)                     'current offset into sample
  497.     r = C.Remainder(K)                 'current remainder(0-255) at 
  498. offset
  499.     v = C.Volume(K)                     'volume(0-64)
  500.  
  501.     h = C.HighStep(K)                   'integer step
  502.     l = C.LowStep(K)                    'remainder step(0-255)
  503.  
  504.     IF C.RepLength(K) THEN
  505.         DO
  506.          'If sample loops the calculate the number of mixes left until
  507.          'we must loop...
  508.             PL&=C.LoopEnd(K)-o
  509.  
  510.          'Thanks to QB's lack of unsigned ints, we must do this...
  511.             IF PL&<0 THEN PL&=PL&+65536
  512.             MixesTillRepeat& = (PL& * 256 - (r+256)) \ ((h * 256&) + l)
  513.  
  514.          'If there is any remainder left, then add 1 to MixesTillRepeat.
  515.             IF ( (PL& * 256 - (r+256)) MOD ((h * 256&) + l) ) THEN
  516.                 MixesTillRepeat&=MixesTillRepeat&+1
  517.             END IF
  518.  
  519.             IF MixesLeftToDo >= MixesTillRepeat& THEN
  520.                 MixesToDo      = MixesTillRepeat&
  521.     )    'for next loop.
  522.                     C.LoopEnd(K) = C.RepStart(K) + C.RepLength(K)
  523.                 ELSE
  524.                     o            = o - C.RepLength(K)
  525.                 ENDIF
  526.             ELSE
  527.                 EXIT DO
  528.             END IF
  529.         LOOP
  530.     END IF
  531.  
  532.     'Mix whatever is left.
  533.     MixesToDo = MixesLeftToDo
  534.     Gosub LowLevelMix
  535.  
  536.     'Store back the offset and its remainder.
  537.     C.Offset(K) = o
  538.     C.Remainder(K) = r
  539.  
  540.     'If sample doesn't loop, and the offset passed the end of the 
  541. sample,
  542.     'then turn off the channel.
  543.     IF C.RepLength(K) = 0 AND UnsignedComp(o, C.Length(K)) > 0 THEN
  544.         C.InactiveFlag(K) = True
  545.     END IF
  546. RETURN
  547.  
  548. LowLevelMix:
  549.     'Copies the samples from the instruments to the mixing buffer.
  550.     'If this is the first copy (Tm=0), then just store the sample,
  551.     'otherwise add it into the buffer.
  552.     If Tm=0 then
  553.         'Since most channels will have a volume of 64 (max), then only
  554.         'use the volume lookup table (which is slow) when needed...
  555.         IF v <> 64 THEN
  556.             FOR OffsetNow = OffsetNow TO OffsetNow + MixesToDo - 1
  557.                 SampleBuffer(OffsetNow) = VolumeTable(v, PEEK(o))
  558.                 o = o + h
  559.                 'If remainder overflows, then increment the offset by 1
  560.                 'and adjust the remainder back down.
  561.                 r = r + l: IF r => 0 THEN r = r - 256: o = o + 1
  562.             NEXT
  563.         ELSE
  564.             FOR OffsetNow = OffsetNow TO OffsetNow + MixesToDo - 1
  565.                 SampleBuffer(OffsetNow) = PEEK(o)
  566.                 o = o + h
  567.                 r = r + l: IF r => 0 THEN r = r - 256: o = o + 1
  568.             NEXT
  569.         END IF
  570.     ELSE
  571.         IF v <> 64 THEN
  572.             FOR OffsetNow = OffsetNow TO OffsetNow + MixesToDo - 1
  573.                 SampleBuffer(OffsetNow) = SampleBuffer(OffsetNow) + _
  574. VolumeTable(v, PEEK(o))
  575.                 o = o + h
  576.                 r = r + l: IF r => 0 THEN r = r - 256: o = o + 1
  577.             NEXT
  578.         ELSE
  579.             FOR OffsetNow = OffsetNow TO OffsetNow + MixesToDo - 1
  580.                 SampleBuffer(OffsetNow)=SampleBuffer(OffsetNow)+PEEK(o)
  581.                 o = o + h
  582.                 r = r + l: IF r => 0 THEN r = r - 256: o = o + 1
  583.             NEXT
  584.         END IF
  585.     END IF
  586. RETURN
  587. '=====================================================================
  588. 'Process the MOD sliding commands...
  589. DoCommands:
  590.     FOR A=0 TO 3
  591.         IF C.Command(A) THEN
  592.             SELECT CASE C.Command(A)
  593.             CASE 1                      'Volume slide
  594.                 C.Volume(A)=C.Volume(A)+C.VolumeSpeed(A)
  595.                 IF C.Volume(A)<0 THEN
  596.                     C.Volume(A)=0
  597.                     C.Command(A)=0
  598.                 ELSEIF C.Volume(A)>64 THEN
  599.                     C.Volume(A)=64
  600.                     C.Command(A)=0
  601.                 END IF
  602.             CASE 2                      'Port down
  603.                 C.Period(A) = C.Period(A) - C.PortSpeed(A)
  604.                 IF C.Period(A)<113 THEN
  605.                     C.Period(A)  = 113
  606.                     C.Command(A) = 0
  607.                 END IF
  608.                 C.HighStep(A)   = PeriodHigh(C.Period(A))
  609.                 C.LowStep(A)    = PeriodLow(C.Period(A))
  610.             CASE 3                      'Port up
  611.                 C.Period(A) = C.Period(A) + C.PortSpeed(A)
  612.                 IF C.Period(A)>1023 THEN
  613.                     C.Period(A) = 1023
  614.                     C.Command(A) = 0
  615.                 END IF
  616.                 C.HighStep(A)   = PeriodHigh(C.Period(A))
  617.                 C.LowStep(A)    = PeriodLow(C.Period(A))
  618.             CASE 4                      'Port to Note Down
  619.                 C.Period(A) = C.Period(A) - C.PortSpeed(A)
  620.                 IF C.Period(A) <= C.PortDest(A) THEN
  621.                     C.Period(A) = C.PortDest(A)
  622.                     C.Command(A) = 0
  623.                 END IF
  624.                 C.HighStep(A)   = PeriodHigh(C.Period(A))
  625.                 C.LowStep(A)    = PeriodLow(C.Period(A))
  626.             CASE 5                      'Port to Note Up
  627.                 C.Period(A) = C.Period(A) + C.PortSpeed(A)
  628.                 IF C.Period(A) >= C.PortDest(A) THEN
  629.                     C.Period(A) = C.PortDest(A)
  630.                     C.Command(A) = 0
  631.                 END IF
  632.                 C.HighStep(A)   = PeriodHigh(C.Period(A))
  633.                 C.LowStep(A)    = PeriodLow(C.Period(A))
  634.             END SELECT
  635.         END IF
  636.     NEXT
  637. RETURN
  638. '=====================================================================
  639. 'Allocates memory from DOS.
  640. FUNCTION Alloc (A)
  641.     CPU.ax = &H4800
  642.     CPU.bx = A
  643.     CALL interruptx(&H21, CPU, CPU)
  644.     IF (CPU.Flags AND 1) THEN ExitWithError "Out of Memory"
  645.     Alloc = CPU.ax
  646. END FUNCTION
  647. '=====================================================================
  648. SUB ExitWithError (A$)
  649.     IF POS(0) <> 1 THEN PRINT
  650.     PRINT A$: END
  651. END SUB
  652. '=====================================================================
  653. 'Extracts a Motorola word from a string, and multiples it by 2.
  654. FUNCTION Extract% (A$, offset)
  655.     v& = (512&*ASC(MID$(A$,offset,1))+ASC(MID$(A$,offset+1, 1)) * 2&)
  656.     IF v& > 65535 THEN ExitWithError "Sample Too Large"
  657.     IF v& > 32767 THEN v& = v& - 65536
  658.     Extract% = v&
  659. END FUNCTION
  660. '=====================================================================
  661. 'Reprograms the 8255 timer so it hits an int 8 at a different rate.
  662. SUB SetInt8Rate (A&)
  663.     OUT &H42, 2 + 4 + 16 + 32
  664.     OUT &H40, CINT(A&) AND 255
  665.     OUT &H40, A& \ 256
  666. END SUB
  667. '=====================================================================
  668. SUB SpeakerOff
  669.     OUT &H61, INP(&H61) AND 252
  670. END SUB
  671. '=====================================================================
  672. SUB SpeakerOn
  673.     OUT &H61,INP(&H61) OR 3:OUT &H43,128+32+16:OUT &H42,0
  674.     OUT &H42,0:OUT &H43,128+16
  675. END SUB
  676. '=====================================================================
  677. ErrorHandler:
  678.     SELECT CASE ERR
  679.     CASE 7
  680.         ExitWithError "Out of Memory Error"
  681.     CASE 52, 53, 68
  682.         ExitWithError "Bad File Name/File not found"
  683.     CASE ELSE
  684.         ExitWithError "Fatal Error"+STR$(ERR)+" has occured!"
  685.     END SELECT
  686. 'END OF QBMP15.BAS====================================================
  687.  
  688. 'QBS Comments: QuickBASIC version complete!
  689.  
  690.  
  691. ════════════════════════════════════════════════════════════════════════════════
  692.  Area:    QuickBasic
  693.   Msg:    #13655
  694.  Date:    11-30-92 21:20 (Public) 
  695.  From:    RICH GELDREICH           
  696.  To:      ALL                      
  697.  Subject: PCSpkr MOD - Assembler Code 
  698. ────────────────────────────────────────────────────────────────────────────────
  699. ;QBMPASM.ASM (Misc. ASM & PC-Speaker routines for QBMP15.BAS)
  700. ;Written by Rich Geldreich (C) Copyright November 27, 1992
  701. ;Assembled with TASM v2.0 (This module hasn't been documented yet.)
  702. IDEAL
  703. MODEL SMALL
  704. CODESEG
  705. PUBLIC  CheckDoneFlag, SetBuffers, GetCS, GetOF, UnsignedComp
  706. EVEN
  707. ASSUME cs:@CODE, ds:@CODE, es:NOTHING, ss:@DATA
  708. ;=====================================================================
  709. Buffer1  dw 0
  710. Buffer2  dw 0
  711. DoneFlag db 0
  712. XLATTable: ;PC-Speaker LOG table
  713. db 32,31,30,29,28,27,26,25,24,24,23,23,22,22,21,21,21,20,20,20,19,19,19
  714. db 18,18,18,18,17,17,17,17,16,16,16,16,15,15,15,15,14,14,14,14,14,13,13
  715. db 13,13,13,12,12,12,12,12,11,11,11,11,11,11,10,10,10,10,10,10,9,9,9,9
  716. db 9,9,8,8,8,8,8,8,7,7,7,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,4,4,4,4,4
  717. db 4,4,3,3,3,3,3,3,3,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,65,65,65,65,65,65,65
  718. db 64,64,64,64,64,64,64,63,63,63,63,63,63,63,62,62,62,62,62,62,62,61,61
  719. db 61,61,61,61,61,60,60,60,60,60,60,60,59,59,59,59,59,59,59,58,58,58,58
  720. db 58,58,57,57,57,57,57,57,56,56,56,56,56,56,55,55,55,55,55,55,54,54,54
  721. db 54,54,53,53,53,53,53,52,52,52,52,52,51,51,51,51,50,50,50,50,49,49,49
  722. db 49,48,48,48,48,47,47,47,46,46,46,45,45,45,44,44,43,43,42,42,41,40,39
  723. db 38,37,36,35,34,33
  724. ;=====================================================================
  725. EVEN
  726. PROC    NewInt8
  727.         Push    ds ax bx
  728. BDS     = $+1
  729.         Mov     ax, 09999h
  730.         Mov     ds, ax
  731. Offset8 = $+1
  732.         Mov     bx, 09999h
  733.         Mov     al, [ds:bx]
  734.         Inc     bx
  735.         Inc     bx
  736.         Mov     [word cs:Offset8], bx
  737. End8    = $+2
  738.         Cmp     bx, 09999h
  739.         Je      @@10
  740. @@Back: Mov     bx, offset XLATTable
  741.         Xlat    [cs:bx]
  742.         Out     042h, al
  743.         Mov     al, 020h
  744.         Out     020h, al
  745.         Pop     bx ax ds
  746.         Iret
  747. EVEN
  748. @@10:   Mov     [byte cs:DoneFlag], -1
  749.         Mov     bx, [cs:Buffer1]
  750.         Xchg    bx, [cs:Buffer2]
  751.         Mov     [cs:Buffer1], bx
  752.         Mov     [word cs:Offset8], bx
  753. BufferSize = $+2
  754.         Add     bx, 09999h
  755.         Mov     [word cs:End8], bx
  756.         Jmp     @@Back
  757. ENDP    NewInt8
  758. ;=====================================================================
  759. EVEN
  760. PROC    CheckDoneFlag
  761.         Xor     al, al
  762.         Xchg    [cs:DoneFlag], al
  763.         Cbw
  764.         Retf    0
  765. ENDP    CheckDoneFlag
  766. ;=====================================================================
  767. PROC    SetBuffers  ;BDS Buffer1 Buffer2 BufferSize
  768. ;                     12   10       8        6
  769.         Push    bp
  770.         Mov     bp, sp
  771.         Push    ds cs
  772.         Pop     ds
  773.         Mov     ax, [ss:bp+8]
  774.         Mov     [ds:Buffer2], ax
  775.         Mov     ax, [ss:bp+12]
  776.         Mov     [word ds:BDS], ax
  777.         Mov     ax, [ss:bp+10]
  778.         Mov     [ds:Buffer1], ax
  779.         Mov     [word ds:Offset8], ax
  780.         Mov     bx, [ss:bp+6]
  781.         Mov     [word ds:BufferSize], bx
  782.         Add     ax, bx
  783.         Mov     [word ds:End8], ax
  784.         Pop     ds bp
  785.         Retf    8
  786. ENDP    SetBuffers
  787. ;=====================================================================
  788. PROC    GetCS
  789.         Mov     ax, cs
  790.         Retf    0
  791. ENDP    GetCS
  792. ;=====================================================================
  793. PROC    GetOF
  794.         Mov     ax, offset NewInt8
  795.         Retf    0
  796. ENDP    GetOF
  797. ;=====================================================================
  798. PROC    UnsignedComp
  799.         Push    bp
  800.         Mov     bp, sp
  801.         Mov     ax, [ss:bp+08]
  802.         Cmp     ax, [ss:bp+06]
  803.         Jae     @@AboveOrEqual
  804.         Xor     ax, ax
  805. @@Back: Pop     bp
  806.         Retf    4
  807. @@AboveOrEqual:
  808.         Mov     ax, 1
  809.         Jmp     @@Back
  810. ENDP    UnsignedComp
  811. ;=====================================================================
  812. END
  813. '<<-Cut Here->>
  814.  
  815. ════════════════════════════════════════════════════════════════════════════════
  816.  Area:    QuickBasic
  817.   Msg:    #13655
  818.  Date:    11-30-92 21:20 (Public) 
  819.  From:    RICH GELDREICH           
  820.  To:      ALL                      
  821.  Subject: PCSpkr MOD - OBJ of ASM code <=> DEBUG
  822. ───────────────────────────────────────────────────────────────────────────────
  823.  
  824. To execute this script, save it to a file and type DEBUG < filename
  825. where "filename" is the name of this script file.
  826. E165"QBMPASM.OBJ" 0
  827. E200".q..92bMh/LMnpa9/BJHcVs5....IJbQWx46/BrQZpaMgJaQU.WJZ7rQdxaPU"
  828. E23D"6X9kY9WH..EdTGEyZl0l7KPk3qQhtGEHpYA6C...ZCHK8...U4W1..EVGdZA."
  829. E27A"E/TFJFMF3/1x2F3N7a5..Gc4U.12UOKm..3w3F/FJE2EIEI3YkMS..6/..2IE"
  830. E2B7".DM70.M.F57pHJ/pWOG..4wj.P/73..E.BA2G3BoG2xYH3N2H/RYJ/.EKE4/."
  831. E2F4".2U0HJ2J0JZF4JYIHxJ..gDYA.../IkF3FpEHhM..k3YA.../IkF3FpH4tM.."
  832. E331"c3YH.../kEJCBJG5tIF2BoHB/ZY/.EA6G...7O.F08f/2..........UwV5Rk"
  833. E36E"l4OY/4MQl3KMF3JI/3IEl2HAV2G6V2F2F2F./2E.l1Dwk1CsU1CsE1BoE1Bk."
  834. E3AB"1Ak.19gk09gk08cU08cU07YE07YE06U.06U.05Qk/5Qk/5MU/4MU/4ME/3IE/"
  835. E3E8"3IE/2E./2E./2Ak.1Ak.1AU.06U.06U./2E./2E./2EE/3IE/3IE./2E./2E."
  836. E425"xnDzwnDzwXDysXDysXDxoHDxoHDxk1Dwk1DwknCvgnCvgnCucXCucXCtYHCtY"
  837. E462"HCsU1CsU1CrQnBrQnBqMXBqMHBpIHBpE1BoE1BnAnAn6XAm6HAl2HAk.1Akwm"
  838. E49F"9jsW9ioG9hk09fgW8eY08bMG7YAW6V.d5EB3iNadXMjPaNes/1BY97ul1/2sy"
  839. E4DC"Na7REgP/.smpa92gUMC6PVp5D1d94P./.wj99u/..smVS6..iYc5..U97ul1/"
  840. E519"2skNad97u/5/gymE81kiMc/2..a9LpWgvV1TgcF6Ae..gcFAAe0/gcF8A8..A"
  841. E556"u1/gcL4Yc5A3k.1D85/wFL8X..AWwmsOE.9LpWgjcF6gXF4Ab/n.QL8H..s4."
  842. E593".fTzvQ42.3X/J/IQ6I3ElnEJ.3b1J/IgDI3El1FJ.3X2J/IQII3ElPFJ.3b4J"
  843. E5D0"/IwPI3ElpFJ.3X5J/IwTI3El2GJ.3z6J/6VW0...o/"
  844. E100 B8 0 3C BA 65 1 33 C9 CD "!rC" BE 0 2 50 BD FB 2 55 BF 88 90
  845. E117 "W3" DB B1 FA 8A F0 80 C1 6 32 E4 AC "<9v" 8 "<Zv" 2 2C 6 2C
  846. E12F 7 2C 2E E3 E8 D3 E0 A C6 AA 2 D8 92 "IIMu" E0 80 FB 0 75 9 5A
  847. E147 59 5B B4 40 CD 21 73 7 B4 9 BA 58 1 CD 21 CD 20 7 "Error!$"
  848. G
  849. Q
  850.  
  851.         That is all.  This program is very big, so take your time
  852. putting it together.  After posting this program, I re-downloaded
  853. all of the messages and combined them just to make sure they were
  854. uploaded correctly, so they will hopefully make it out to the inner
  855. recesses of the FidoNET intact...
  856.  
  857.         I have spent _many_ months optimizing my MOD playing
  858. algorithms so they would be efficient enough to do in almost all
  859. QB.  On my 286/10, I've rated it to use about 95% processor time at
  860. 11,000 hertz.  This may not sound like much, but the all-assembler
  861. version of this algorithm(which is used in my first demo that we
  862. will be releasing under Renaissance) is extremely efficient - it can
  863. play any MOD up to 65,535 hertz on my 286/10 (and still have about
  864. 25% processor time remaining!).  This test was done on my Tandy's
  865. DMA DAC, which can play at very high sample rates.
  866.  
  867.         As I said in QBMP15.BAS, I currently have not been able to
  868. test this program in QuickBASIC 4.5, only PDS.
  869.  
  870.         Have fun! 
  871.  
  872.         Rich Geldreich
  873.  
  874.     PS. Some of the docs in the QB program wrapped, so be carefull 
  875. reconstucted the code.
  876.  
  877. --- MsgToss 2.0b
  878.  * Origin: Computer Co-Op - Voorhees, NJ | Ted Hare (1:266/29)
  879.  
  880.  
  881.